package gui.batches;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Timer;

import gui.batches.command.CommandHistory;
import gui.batches.command.RemoveItemsCommand;
import gui.common.*;
import gui.item.ItemData;
import gui.product.*;
import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemFacade;
import hit.core.Product;
import hit.dao.DataAccessObject;
import hit.dao.FactoryReference;
import hit.util.DataConverter;

/**
 * Controller class for the remove item batch view.
 */
public class RemoveItemBatchController extends Controller implements
		IRemoveItemBatchController {
	
	private static final int BARCODE_TIMEOUT = 750;
	
	private ItemFacade itemFacade;
	private CommandHistory commandHistory;
	
	private boolean useTimer;
	private Timer timer;
	
	private List<Item> removedItems;
	private Map<ProductData, List<ItemData>> removedItemsMapping;
	
	
	/**
	 * Constructor.
	 * 
	 * @param view Reference to the remove item batch view.
	 */
	public RemoveItemBatchController(IView view) {
		super(view);
		
		itemFacade = HomeInventoryTracker.Instance().GetItemFacade();
		commandHistory = new CommandHistory();
		
		useTimer = true;
		timer = null;
		
		removedItems = new ArrayList<Item>();
		removedItemsMapping = new HashMap<ProductData, List<ItemData>>();

		construct();
	}
	
	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected IRemoveItemBatchView getView() {
		return (IRemoveItemBatchView)super.getView();
	}

	/**
	 * Loads data into the controller's view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() {
		getView().setUseScanner(useTimer);
	}

	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() {
		//get the itemdata to remove
		String barcode = getView().getBarcode();
		
		//if the barcode was empty, just disable the remove item button and return
		if (barcode.equals("") || getView().getUseScanner()) {
			getView().enableItemAction(false);
		}
		else {
			getView().enableItemAction(true);
		}
		
		//check if undo should be enabled
		if (commandHistory.CanUndo())
			getView().enableUndo(true);
		else
			getView().enableUndo(false);
		
		//check if redo should be enabled
		if (commandHistory.CanRedo()) 
			getView().enableRedo(true);
		else
			getView().enableRedo(false);
	}

	/**
	 * This method is called when the "Item Barcode" field is changed
	 * in the remove item batch view by the user.
	 */
	@Override
	public void barcodeChanged() {
		//reset the timer
		if (timer != null) {
			timer.stop();
			timer = null;
		}
		
		String newBarcodeValue = getView().getBarcode();  
		if (newBarcodeValue != "")
		{
			if (useTimer)
			{
				timer = new Timer(BARCODE_TIMEOUT, new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						removeItem();
					}
					
				});
				timer.start();
			}
		}

		enableComponents();
	}
	
	/**
	 * This method is called when the "Use Barcode Scanner" setting is changed
	 * in the remove item batch view by the user.
	 */
	@Override
	public void useScannerChanged() {
		useTimer = getView().getUseScanner();
		
		if (!useTimer) {
			if (timer != null)
			{
				timer.stop();
				timer = null;
			}
		}
		
		getView().setBarcode("");
		enableComponents();
	}
	
	/**
	 * This method is called when the selected product changes
	 * in the remove item batch view.
	 */
	@Override
	public void selectedProductChanged() {
		//display the items from the selected product
		if (getView().getSelectedProduct() != null) {
			ItemData[] items = removedItemsMapping.get(getView().getSelectedProduct()).
																toArray(new ItemData[0]);
			getView().setItems(items); 
		}
		
		//display the updated product information
		ProductData[] products = removedItemsMapping.keySet().toArray(new ProductData[0]);
		ProductData currentlySelected = getView().getSelectedProduct();
		getView().setProducts(products);
		getView().selectProduct(currentlySelected);
	}
	
	/**
	 * This method is called when the user clicks the "Remove Item" button
	 * in the remove item batch view.
	 */
	@Override
	public void removeItem() {
		//get the itemdata to remove
		String barcode = getView().getBarcode();
		Item item = itemFacade.GetItemByBarcode(barcode);
		
		//if the item wasn't found, show an error and return
		if (item == null) {
			getView().displayErrorMessage("The specified item does not exist");
			getView().setBarcode("");
			enableComponents();
			timer.stop();
			timer = null;
			return;
		}
		
		//actually remove the item from the hit
		List<Item> itemsToRemove = new ArrayList<Item>();
		itemsToRemove.add(item);
		commandHistory.Execute(new RemoveItemsCommand(itemsToRemove));
		removedItems.add(item);
				
		//if we're on barcode mode, stop the timer
		if (useTimer) {
			timer.stop();
			timer = null;
		}
		
		//update the view
		UpdateView(item, true);
		getView().setBarcode("");
		getView().giveBarcodeFocus();
		enableComponents();
	}
	
	/**
	 * Update the View after a remove, undo, or redo
	 * @param item the removed item
	 * @param removed true if an item was removed, false if an item was added back in
	 * */
	private void UpdateView(Item item, boolean removed) {
		//find the product data associated with the item
		ProductData productData = null;
		for (ProductData data: removedItemsMapping.keySet()) {
			if (data.getTag().equals(item.GetProduct()))
				productData = data;
		}
		
		//if the productdata is already in the mapping, add (or remove) the item to the list
		if (productData != null) {
			if (removed)
				removedItemsMapping.get(productData).add(DataConverter.TranslateItem(item));
			else
				removedItemsMapping.get(productData).remove(DataConverter.TranslateItem(item));
		}
		//otherwise, start a new list of items to be removed
		else {
			productData = DataConverter.TranslateProduct(item.GetProduct());
			List<ItemData> items = new ArrayList<ItemData>();
			items.add(DataConverter.TranslateItem(item));
			removedItemsMapping.put(productData, items);
		}
		
		//update the count of items removed
		String newCount = Integer.toString(removedItemsMapping.get(productData).size());
		productData.setCount(newCount);
		
		selectedProductChanged();
	}
	
	/**
	 * This method is called when the user clicks the "Redo" button
	 * in the remove item batch view.
	 */
	@Override
	public void redo() {
		List<Item> items = commandHistory.Redo();
		UpdateView(items.get(0), true);
		removedItems.addAll(items);
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in the remove item batch view.
	 */
	@Override
	public void undo() {
		List<Item> items = commandHistory.Undo();
		UpdateView(items.get(0), false);
		removedItems.removeAll(items);
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Done" button
	 * in the remove item batch view.
	 */
	@Override
	public void done() {
		
		ArrayList<Product> productsUpdated = new ArrayList<Product>();
		
		DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
		dao.StartTransaction();
		
		for (Item i : removedItems)
		{
			dao.GetItemDAO().Update(i);
			
			dao.EndTransaction(true);
			
			if (!productsUpdated.contains(i.GetProduct()))
			{
				productsUpdated.add(i.GetProduct());
			}
		}
		
		for (Product p : productsUpdated)
		{
			dao.GetProductDAO().Update(p);
		}
		
		dao.CloseConnection();
		
		getView().close();
	}

}

